﻿//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the Program class.</summary>
//-----------------------------------------------------------------------

////#define EVAL_COUNTS
////#define BIND_FUNCTION_PERF
////#define DEMOBUILD
namespace Tulip.Benchmarks.Manners
{
    using System;
    using System.Collections;
    using System.IO;
    using System.Text.RegularExpressions;
    using Model;
    using Tulip.Manners;

    /// <summary>
    /// Manners text program.
    /// </summary>
    public class Program
    {
        /// <summary>
        /// Count of the number of times the test has been run.
        /// </summary>
        private static int counter;

        /// <summary>
        /// Performs the Miss Manners benchmark test using a rule set obtained from a managed resource.
        /// </summary>
        /// <param name="guests">
        /// Array of Guest facts.
        /// </param>
        private static void PerformTest(Guest[] guests)
        {
            counter = 0;
            ////////Tulip.Engine.AssertTicks = 0;
            ////////Tulip.Engine.AssertManyTicks = 0;
            ////////Tulip.Engine.RetractTicks = 0;
            ////////Tulip.Engine.RetractManyTicks = 0;
            ////////Tulip.Engine.QuantifyTicks = 0;
            ////////Tulip.Engine.FireTicks = 0;

            var stdioOut = Console.Out;

            // Populate the fact array with Guest facts
            var lenGuests = guests.Length + 2;
            var facts = new object[lenGuests];
            guests.CopyTo(facts, 0);

            // Initialise the Context and Count facts
            Context.State = "start";
            var cnt = new Count();
            Count.C = 1;

            // Add additional facts to fact array
            facts[lenGuests - 3] = new LastSeat();
            facts[lenGuests - 2] = cnt;
            facts[lenGuests - 1] = new Context();

            var exec = new MannersExecutor();

            ////Tulip.Tuples.TupleStoreSession.AssertDuration = new TimeSpan();
            ////Tulip.Tuples.TupleStoreSession.RetractDuration = new TimeSpan();
            ////Tulip.Tuples.TupleStoreSession.ReleaseNo = 0;

            // Execute the rule set with timings

            ////System.Runtime.GCSettings.LatencyMode = System.Runtime.GCLatencyMode.LowLatency;

            var startTicks = DateTime.Now.Ticks;
            exec.Execute(facts);
            var endTicks = DateTime.Now.Ticks;

            ////System.Runtime.GCSettings.LatencyMode = System.Runtime.GCLatencyMode.Interactive;

            // Print out the results
            stdioOut.WriteLine("\r\nAll Done");
            stdioOut.WriteLine("Elapsed time: " + FormatElapsedDuration(startTicks, endTicks));

            System.Diagnostics.Trace.WriteLine((++counter) + "," + (endTicks - startTicks));

            ////////tOut.WriteLine("AssertTicks time: " + Tulip.Engine.AssertTicks);
            ////////tOut.WriteLine("AssertManyTicks time: " + Tulip.Engine.AssertManyTicks);
            ////////tOut.WriteLine("RetractTicks time: " + Tulip.Engine.RetractTicks);
            ////////tOut.WriteLine("RetractManyTicks time: " + Tulip.Engine.RetractManyTicks);
            ////////tOut.WriteLine("QuantifyTicks time: " + Tulip.Engine.QuantifyTicks);
            ////////tOut.WriteLine("FireTicks time: " + Tulip.Engine.FireTicks);

            //////////tOut.WriteLine("Retract time: " + Tulip.Tuples.TupleStoreSession.AssertDuration.ToString());
            //////tOut.WriteLine("Retract time: " + Tulip.Tuples.TupleStoreSession.RetractDuration.ToString());
            ////////tOut.WriteLine("Retract PB time: " + Tulip.Tuples.TupleStoreSession.RetractPb.ToString());
            /////tOut.WriteLine("Release count: " + Tulip.Tuples.TupleStoreSession.ReleaseNo.ToString());

            ////tOut.WriteLine("Not1aInvocations: " + MannersExecutor.Not1aInvocations);
            ////tOut.WriteLine("Not1aTuplesFound: " + MannersExecutor.Not1aTuplesFound);
            ////tOut.WriteLine("Not1aTuples_1_Found: " + MannersExecutor.Not1aTuples_1_Found);
            ////tOut.WriteLine("Not1aTuples_2_Found: " + MannersExecutor.Not1aTuples_2_Found);
            ////tOut.WriteLine("Not1aTuples_1_Loops: " + MannersExecutor.Not1aTuples_1_Loops);
            ////tOut.WriteLine("Not1aTuples_2_Loops: " + MannersExecutor.Not1aTuples_2_Loops);
            ////tOut.WriteLine("Not1aMatches_1_Found: " + MannersExecutor.Not1aMatches_1_Found);
            ////tOut.WriteLine("Not1aTuplesOut: " + MannersExecutor.Not1aTuplesOut);

#if BIND_FUNCTION_PERF
            tOut.WriteLine("\r\nPERFORMANCE===================================================");
            foreach (KeyValuePair<string, TimeSpan> kvp in Tulip.Tuples.TupleList.BindFuncPerf)
            {
                tOut.WriteLine(kvp.Key + " elapsed time: " + kvp.Value.TotalMilliseconds.ToString());
            }
#endif
#if BIND_FUNCTION_PERF

            tOut.WriteLine("\r\nCOUNTS========================================================");
            foreach (KeyValuePair<string, int> kvp in MannersExecutor.EvalCounts)
            {
                tOut.WriteLine(kvp.Key + " eval count: " + kvp.Value.ToString());
            }
#endif

            stdioOut.WriteLine("\r\n==============================================================");
        }

        /// <summary>
        /// Formats and returns the 'Elapsed Time' string based on the tick values provided.
        /// </summary>
        /// <param name="startTicks">Tick count at start of test.</param>
        /// <param name="endTicks">Tick count at end of test.</param>
        /// <returns>Formatted string containing the elapsed time for the benchmark test.</returns>
        private static string FormatElapsedDuration(long startTicks, long endTicks)
        {
            // Format the elapsed time
            var ts = new TimeSpan(endTicks - startTicks);
            var days = ts.Days;
            ts = ts.Subtract(new TimeSpan(days, 0, 0, 0, 0));
            var hours = ts.Hours;
            ts = ts.Subtract(new TimeSpan(0, hours, 0, 0, 0));
            var minutes = ts.Minutes;
            ts = ts.Subtract(new TimeSpan(0, 0, minutes, 0, 0));
            var seconds = ts.Seconds;
            ts = ts.Subtract(new TimeSpan(0, 0, 0, seconds, 0));
            var milliseconds = ts.Milliseconds;

            var elapsedDur = string.Empty;

            if (days > 0)
            {
                elapsedDur = hours + " hours";
            }

            if (minutes > 0)
            {
                if (elapsedDur.Length > 0)
                {
                    elapsedDur += ", ";
                }

                elapsedDur += minutes + " minutes";
            }

            if (seconds > 0)
            {
                if (elapsedDur.Length > 0)
                {
                    elapsedDur += ", ";
                }

                if (milliseconds > 0)
                {
                    elapsedDur += (seconds + (milliseconds / 1000D)).ToString();
                }
                else
                {
                    elapsedDur += seconds.ToString();
                }

                elapsedDur += " seconds";
            }
            else
            {
                if (milliseconds > 0)
                {
                    elapsedDur += (milliseconds / 1000D) + " second";
                }
                else if (elapsedDur.Length > 0)
                {
                    elapsedDur = "0." + ((endTicks - startTicks) / TimeSpan.TicksPerMillisecond) + " seconds";
                }
            }

            return elapsedDur;
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        /// <param name="args">
        /// The args.
        /// </param>
        /// <remarks>
        /// Pass the path and file name for an OPS5 .dat (data) file as an argument to the 
        /// application.   Tghe code uses regular expressions to parse the file and create
        /// corresponding fact objects using the 'Model' class library.
        /// </remarks>
        [STAThread]
        private static void Main(string[] args)
        {
#if DEMOBUILD
            var path = string.Empty;
#else
            const string path = @"..\..\Data\";
#endif
            var stdioOut = Console.Out;
            var stdioIn = Console.In;
            var breakPrompt = false;
            const string prompt = "MissManners> ";
            var bypassPrompt = false;
            var filePath = string.Empty;

            if (args.Length > 0)
            {
                filePath = args[0];
                bypassPrompt = Convert.ToString(args[0]).Trim().Length > 0;
            }

            while (!breakPrompt)
            {
                if (!bypassPrompt)
                {
                    stdioOut.Write(prompt);

                    var input = stdioIn.ReadLine().Trim().ToLower();
                    ////input = input.Substring(prompt.Length);

                    if (input == string.Empty)
                    {
                        continue;
                    }

                    switch (input)
                    {
                        case "(batch 4)":
                            filePath = path + "manners_4.dat";
                            break;
                        case "(batch 8)":
                            filePath = path + "manners_8.dat";
                            break;
                        case "(batch 16)":
                            filePath = path + "manners_16.dat";
                            break;
                        case "(batch 32)":
                            filePath = path + "manners_32.dat";
                            break;
                        case "(batch 64)":
                            filePath = path + "manners_64.dat";
                            break;
                        case "(batch 128)":
                            filePath = path + "manners_128.dat";
                            break;
                        case "(exit)":
                            breakPrompt = true;
                            continue;
                        default:
                            stdioOut.WriteLine("Command not recognised.");
                            continue;
                    }
                }
                else
                {
                    breakPrompt = true;
                }

                for (var idx = 0; idx < 1; idx++)
                {
                    // Get the file path for the dat file
                    if (filePath.Length > 0)
                    {
                        try
                        {
                            var fileInfoDat = new FileInfo(filePath);

                            if (fileInfoDat.Exists)
                            {
                                // Create an array of Guest facts over the contents of the .dat file
                                var guestEntries = new ArrayList();

                                var sr = new StreamReader(fileInfoDat.OpenRead());
                                var nextLine = sr.ReadLine();

                                while (nextLine != null)
                                {
                                    if (Regex.IsMatch(nextLine, "^\\(make\\sguest\\s"))
                                    {
                                        var nextGuest = new Guest
                                                        {
                                                            Name = Regex.Match(nextLine, "\\^name\\s(?<1>\\w*)\\s").Groups[1].Value,
                                                            Sex = Regex.Match(nextLine, "\\^sex\\s(?<1>[mf])\\s").Groups[1].Value,
                                                            Hobby = Regex.Match(nextLine, "\\^hobby\\s(?<1>\\w*)\\)").Groups[1].Value
                                                        };

                                        guestEntries.Add(nextGuest);
                                    }
                                    else if (Regex.IsMatch(nextLine, "^\\(make\\slast_seat\\s"))
                                    {
                                        LastSeat.Seat = Convert.ToInt32(Regex.Match(nextLine, "\\s(?<1>[0-9]*)\\)").Groups[1].Value);
                                    }

                                    nextLine = sr.ReadLine();
                                }

                                var guests = new Guest[guestEntries.Count];
                                guestEntries.CopyTo(guests);
                                const int testCount = 1;

                                for (var i = 0; i < testCount; i++)
                                {
                                    // Pass the array of Guest facts to the engine to perform the test
                                    PerformTest(guests);

                                    if (i != testCount - 1)
                                    {
                                        continue;
                                    }

                                    Console.Write(@"Do you want to repeat the test: [Y/N]: ");

                                    var nextKey = Console.ReadKey();
                                    var repeatTest = nextKey.KeyChar == 'y' || nextKey.KeyChar == 'Y';

                                    i = repeatTest ? -1 : i;
                                }

                                Console.WriteLine(string.Empty);
                            }
                            else
                            {
                                stdioOut.WriteLine("You must provide the path and name of a valid .dat file\n\n");
                            }
                        }
                        catch (Exception ex)
                        {
                            stdioOut.WriteLine("Could not process the .dat file\n\n");
                            stdioOut.WriteLine(ex + "\n\n");
                        }
                    }
                    else
                    {
                        stdioOut.WriteLine("You must provide the path and name of a valid .dat file\n\n");
                    }
                }
            }
        }
    }
}
